﻿using System;
using System.IO;
using System.Globalization;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Forms;

using SharpGL.Enumerations;
using SharpGL.Media3D;
using SharpGL.SceneGraph;
using SharpGL.SceneGraph.Assets;
using SharpGL.SceneGraph.Effects;
using SharpGL.SceneGraph.Primitives;
using SharpGL.SceneGraph.Primitives.Text;
using SharpGL.SceneGraph.Primitives.SmartAxies;

namespace SharpGL.Serialization
{
    [Export(typeof(IFileFormat))]
    public class ModelFileFormat : IFileFormat
    {
        #region Private Types

        private enum ObjectType : uint
        {
            None,
            PointList,
            Directions,
            Polyline,
            SolidPolyline,
            Surface,
            TriangleList,
            TexturedQuad,
            Text2D,
            Text3D
        }

        private enum FillType : uint
        {
            Auto,
            Solid,
            Levels
        }

        private class VertexInfo : TriangleNet.Data.Vertex
        {
            #region Private Fields

            private GLColor color = new GLColor(0.0f, 0.0f, 0.0f, 1.0f);
            private int normalIndex = -1;
            private int textureCoordinateIndex = -1;
            private bool used = true;

            #endregion Private Fields

            #region Public Constructors

            public VertexInfo(GLColor color, Point3D position, int normalIndex = - 1, int textureCoordinateIndex = -1, bool used = true)
                : base(position.X, position.Y, 0, 1)
            {
                this.color = color;
                this.Attributes[0] = position.Z;
                this.normalIndex = normalIndex;
                this.textureCoordinateIndex = textureCoordinateIndex;
                this.used = used;
            }

            #endregion Public Constructors

            #region Public Properties

            public GLColor Color
            {
                get { return color; }
            }

            public Point3D Position
            {
                get { return new Point3D((float)this.X, (float)this.Y, (float)this.Attributes[0]); }
            }

            public int NormalIndex
            {
                get { return normalIndex; }
            }

            public int TextureCoordinateIndex
            {
                get { return textureCoordinateIndex; }
            }

            public bool Used
            {
                get { return used; }
            }

            #endregion Public Properties
        }

        private class ObjectData
        {
            #region Private Fields

            private ObjectType objectType;

            private float? pointSize;
            private float? lineWidth;

            private int? slices;
            private int? stacks;

            private float? angle;
            private string text;

            private string textureFilePath;

            private FillType? fillType;

            private List<VertexInfo> vertices = new List<VertexInfo>();
            private List<Vector3D> normals = new List<Vector3D>();
            private List<UV> textureCoordinates = new List<UV>();
            private List<Index> indices = new List<Index>();
            private List<Face> faces = new List<Face>();
            private Dictionary<float, GLColor> colorLevels = new Dictionary<float, GLColor>();

            #endregion Private Fields

            #region Public Constructors

            public ObjectData()
            {
                Reset();
            }

            #endregion Public Constructors

            #region Public Methods

            public void Validate()
            {
                List<Index> validatedIndices = new List<Index>();
                foreach (Index index in indices)
                {
                    if (((index.Vertex >= 0) && (index.Vertex < vertices.Count)) &&
                        ((index.Normal < 0) || (index.Normal < normals.Count)) &&
                        ((index.UV < 0) || (index.UV < textureCoordinates.Count)))
                        validatedIndices.Add(index);
                }
                indices = validatedIndices;

                List<Face> validatedFaces = new List<Face>();
                foreach (Face face in faces)
                {
                    Face validatedFace = new Face();
                    foreach (Index index in face.Indices)
                    {
                        if (indices.IndexOf(index) >= 0)
                            validatedFace.Indices.Add(index);
                    }

                    if (validatedFace.Count == 3)
                        validatedFaces.Add(validatedFace);
                }
                faces = validatedFaces;

                colorLevels.OrderBy(colorLevel => colorLevel.Key);
            }

            public void Reset()
            {
                objectType = ObjectType.None;

                pointSize = null;
                lineWidth = null;

                slices = null;
                stacks = null;

                angle = null;
                text = null;

                textureFilePath = null;

                fillType = FillType.Solid;

                vertices.Clear();
                normals.Clear();
                textureCoordinates.Clear();
                indices.Clear();
                faces.Clear();
                colorLevels.Clear();
            }

            #endregion PublicMethods

            #region Public Properties

            public ObjectType ObjectType
            {
                get { return objectType; }
                set { objectType = value; }
            }

            public float? PointSize
            {
                get { return pointSize; }
                set { pointSize = value; }
            }

            public float? LineWidth
            {
                get { return lineWidth; }
                set { lineWidth = value; }
            }

            public int? Slices
            {
                get { return slices; }
                set { slices = value; }
            }

            public int? Stacks
            {
                get { return stacks; }
                set { stacks = value; }
            }

            public float? Angle
            {
                get { return angle; }
                set { angle = value; }
            }

            public string Text
            {
                get { return text; }
                set { text = value; }
            }

            public string TextureFilePath
            {
                get { return textureFilePath; }
                set { textureFilePath = value; }
            }

            public FillType? Fill
            {
                get { return fillType; }
                set { fillType = value; }
            }

            public List<VertexInfo> Vertices
            {
                get { return vertices; }
            }

            public List<Vector3D> Normals
            {
                get { return normals; }
            }

            public List<UV> TextureCoordinates
            {
                get { return textureCoordinates; }
            }

            public List<Index> Indices
            {
                get { return indices; }
            }

            public List<Face> Faces
            {
                get { return faces; }
            }

            public Dictionary<float, GLColor> ColorLevels
            {
                get { return colorLevels; }
            }

            #endregion Public Properties
        }

        #endregion Private Types

        #region Private Fields

        internal readonly char[] splitSeparators = new char[] { ' ', '\t', ',' };
        internal readonly NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;

        private static GLColor defaultColor = new GLColor(1.0f, 1.0f, 1.0f, 1.0f);
        private static Vector3D defaultNormal = new Vector3D(0.0f, 0.0f, 1.0f);
        private static Vector3D default3DTextDirection = new Vector3D(0.0f, 1.0f, 0.0f);
        private static float defaultPointSize = 1.0f;
        private static float defaultLineWidth = 1.0f;
        private static float defaultAngle = 0.0f;
        private static int defaultSlices = 3;
        private static int defaultStacks = 3;
        private static System.Drawing.Size defaultTexturedQuadCellSize = new System.Drawing.Size(512, 512);

        #endregion Private Fields

        #region Private Methods

        private SceneElement CreateSceneElement(ObjectData objectData)
        {
            SceneElement sceneElement;

            switch (objectData.ObjectType)
            {
                case ObjectType.Directions:
                    sceneElement = CreateDirectionsList(objectData);
                    break;

                case ObjectType.PointList:
                    sceneElement = CreatePointList(objectData);
                    break;

                case ObjectType.Polyline:
                    sceneElement = CreatePolyline(objectData);
                    break;

                case ObjectType.SolidPolyline:
                    sceneElement = CreateSolidPolyline(objectData);
                    break;

                case ObjectType.Surface:
                    sceneElement = CreateSurface(objectData);
                    break;

                case ObjectType.Text2D:
                    sceneElement = CreateText2D(objectData);
                    break;

                case ObjectType.Text3D:
                    sceneElement = CreateText3D(objectData);
                    break;

                case ObjectType.TexturedQuad:
                    sceneElement = CreateTexturedQuad(objectData);
                    break;

                case ObjectType.TriangleList:
                    sceneElement = CreateTriangleList(objectData);
                    break;

                default:
                    throw new NotImplementedException();
            }

            return sceneElement;
        }

        private Folder CreateDirectionsList(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 1)
                throw new Exception("Not enough data to create directions list.");

            Folder directionsList = new Folder()
                {
                    Name = "Directions list"
                };

            foreach (var vertexInfo in objectData.Vertices)
            {
                var direction = new Trend();

                direction.Position = vertexInfo.Position;
                if (vertexInfo.NormalIndex == -1)
                    throw new Exception("Not enough data to create direction.");
                else
                    direction.Direction = objectData.Normals[vertexInfo.NormalIndex];
                direction.Slices = (objectData.Slices.HasValue ? objectData.Slices.Value : defaultSlices);
                direction.Stacks = (objectData.Stacks.HasValue ? objectData.Stacks.Value : defaultStacks);

                var directionEffect = new OpenGLAttributesEffect()
                {
                    Name = "Trend attributes"
                };
                directionEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
                directionEffect.AttributesGroup.CurrentAttributes.CurrentColor = vertexInfo.Color;
                directionEffect.AttributesGroup.PointAttributes.Size = (objectData.PointSize.HasValue ? objectData.PointSize.Value : defaultPointSize);

                direction.Effects.Add(directionEffect);

                directionsList.Children.Add(direction);
            }

            return directionsList;
        }

        private Polygon CreatePointList(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 1)
                throw new Exception("Not enough data to create point list.");

            var pointList = new Polygon()
                {
                    Name = "Points list"
                };
            for (int vertexIndex = 0; vertexIndex < objectData.Vertices.Count; vertexIndex++)
            {
                pointList.Vertices.Add(objectData.Vertices[vertexIndex].Position);

                Face face = new Face();
                face.Indices.Add(new Index(vertexIndex));
                face.Material = new Material()
                {
                    Diffuse = objectData.Vertices[vertexIndex].Color,
                    Ambient = objectData.Vertices[vertexIndex].Color
                };

                pointList.Faces.Add(face);
            }

            var pointListEffect = new OpenGLAttributesEffect()
                {
                    Name = "Point list attributes"
                };
            pointListEffect.AttributesGroup.PointAttributes.Size = objectData.PointSize.HasValue ? objectData.PointSize.Value : defaultPointSize;
            pointListEffect.AttributesGroup.PointAttributes.Smooth = true;

            pointList.Effects.Add(pointListEffect);

            return pointList;
        }

        private Polyline CreatePolyline(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 2)
                throw new Exception("Not enough data to create polyline.");

            var polyline = new Polyline()
            {
                Name = "Polyline"
            };

            polyline.GenerateNormals = true;
            foreach (var vertexInfo in objectData.Vertices)
            {
                polyline.Vertices.Add(vertexInfo.Position);
            }

            var polylineEffect = new OpenGLAttributesEffect()
            {
                Name = "Polyline attributes"
            };
            polylineEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            polylineEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;
            polylineEffect.AttributesGroup.LineAttributes.Width = (objectData.LineWidth.HasValue ? objectData.LineWidth : defaultLineWidth);

            polyline.Effects.Add(polylineEffect);

            return polyline;
        }

        private SolidPolyline CreateSolidPolyline(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 2)
                throw new Exception("Not enough data to create solid polyline.");

            var solidPolyline = new SolidPolyline()
            {
                Name = "Solid polyline"
            };

            solidPolyline.GenerateNormals = true;
            solidPolyline.Slices = (objectData.Slices.HasValue ? objectData.Slices.Value : defaultSlices);
            solidPolyline.Stacks = (objectData.Stacks.HasValue ? objectData.Stacks.Value : defaultStacks);
            foreach (var vertexInfo in objectData.Vertices)
            {
                solidPolyline.Vertices.Add(vertexInfo.Position);
            }

            var SolidPolylineEffect = new OpenGLAttributesEffect()
            {
                Name = "Solid polyline attributes"
            };
            SolidPolylineEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            SolidPolylineEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;
            SolidPolylineEffect.AttributesGroup.LineAttributes.Width = (objectData.LineWidth.HasValue ? objectData.LineWidth : defaultLineWidth);

            solidPolyline.Effects.Add(SolidPolylineEffect);

            return solidPolyline;
        }

        private Polygon CreateSurface(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 3)
                throw new Exception("Not enough data to create surface.");

            var surface = PolygonFromVerticesSet(objectData.Vertices);

            if (objectData.Fill == FillType.Auto || objectData.Fill == FillType.Levels)
            {
                objectData.ColorLevels.OrderBy(colorLevel => colorLevel.Key);

                Rect3D surfaceBoundingRect = surface.BoundingVolume.BoundingRect;
                
                foreach (var face in surface.Faces)
                {
                    float value = 0.0f;
                    foreach (var index in face.Indices)
                        value += surface.Vertices[index.Vertex].Z;
                    value /= face.Indices.Count;

                    GLColor faceColor = new GLColor();
                    if (objectData.Fill == FillType.Auto)
                        faceColor = MapValueToGLColor(value, surfaceBoundingRect.Z, surfaceBoundingRect.Z + surfaceBoundingRect.SizeZ);
                    else if (objectData.Fill == FillType.Levels)
                        faceColor = objectData.ColorLevels.FirstOrDefault(colorLevel => colorLevel.Key >= value).Value;

                    face.Material = new Material()
                    {
                        Diffuse = faceColor
                    };
                }
            }
            else
            {
                surface.Material = new Material()
                {
                    Diffuse = objectData.Vertices[0].Color
                };
            }

            var surfaceEffect = new OpenGLAttributesEffect()
                {
                    Name = "Surface attributes"
                };
            //if (objectData.Fill == FillType.Solid)
            //    surfaceEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            //surfaceEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;
            surfaceEffect.AttributesGroup.ColorBufferAttributes.BlendingSourceFactor = BlendingSourceFactor.SourceAlpha;
            surfaceEffect.AttributesGroup.ColorBufferAttributes.BlendingDestinationFactor = BlendingDestinationFactor.OneMinusSourceAlpha;
            surfaceEffect.AttributesGroup.ColorBufferAttributes.EnableBlend = true;
            //surfaceEffect.AttributesGroup.EnableAttributes.EnableNormalize = true;
            //surfaceEffect.AttributesGroup.PolygonAttributes.PolygonMode = PolygonMode.Points;

            //var surfaceTransformationEffect = new LinearTransformationEffect();
            //surfaceTransformationEffect.LinearTransformation.ScaleZ = 5.0f;
            //surfaceTransformationEffect.LinearTransformation.TranslateZ = surface.BoundingVolume.Centre.Z * (1.0f - surfaceTransformationEffect.LinearTransformation.ScaleZ);

            surface.Effects.Add(surfaceEffect);
            //surface.Effects.Add(surfaceTransformationEffect);

            return surface;
        }

        private Text2DObject CreateText2D(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 1 || string.IsNullOrWhiteSpace(objectData.Text))
                throw new Exception("Not enough data to create 2D text object.");

            var text2D = new Text2DObject()
                {
                    Name = "2D text object",
                    Font = new System.Drawing.Font("Arial", 10.0f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 204),
                    Text = objectData.Text,
                    Position = objectData.Vertices[0].Position,
                    HorizontalAlignment = SceneGraph.Primitives.Text.HorizontalAlignment.Left,
                    VerticalAlignment = SceneGraph.Primitives.Text.VerticalAlignment.Bottom
                };

            var text2DEffect = new OpenGLAttributesEffect()
                {
                    Name = "2D text object attributes"
                };
            text2DEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;

            text2D.Effects.Add(text2DEffect);

            return text2D;
        }

        private Text3DObject CreateText3D(ObjectData objectData)
        {
            if (objectData.Vertices.Count < 1 || string.IsNullOrWhiteSpace(objectData.Text))
                throw new Exception("Not enough data to create 3D text object.");

            var text3D = new Text3DObject()
                {
                    Name = "3D text object",
                    //Font = new System.Drawing.Font("Arial", 1.0f, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, 204),
                    Text = objectData.Text,
                    Position = objectData.Vertices[0].Position,
                    HorizontalAlignment = SceneGraph.Primitives.Text.HorizontalAlignment.Left,
                    VerticalAlignment = SceneGraph.Primitives.Text.VerticalAlignment.Bottom,
                    DepthAlignment = SceneGraph.Primitives.Text.DepthAlignment.Center,
                    Extrusion = 1.0f,
                    Direction = (objectData.Vertices[0].NormalIndex == -1 ? default3DTextDirection : objectData.Normals[objectData.Vertices[0].NormalIndex]),
                    Angle = (objectData.Angle.HasValue ? objectData.Angle.Value : defaultAngle)
                };

            var text3DEffect = new OpenGLAttributesEffect()
                {
                    Name = "3D text object attributes"
                };
            text3DEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            text3DEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;

            text3D.Effects.Add(text3DEffect);

            return text3D;
        }

        private TexturedQuad CreateTexturedQuad(ObjectData objectData)
        {
            if (objectData.TextureFilePath == null || objectData.Vertices.Count < 4)
                throw new Exception("Not enough data to create textured quad.");

            var texturedQuad = new TexturedQuad()
                {
                    Name = "Textured quad",
                    A = (Point3D)objectData.Vertices[0].Position,
                    B = (Point3D)objectData.Vertices[1].Position,
                    C = (Point3D)objectData.Vertices[2].Position,
                    D = (Point3D)objectData.Vertices[3].Position,
                    CellSize = defaultTexturedQuadCellSize,
                    Material = new Material()
                    {
                        Texture = new Texture2D()
                        {
                            Image = new System.Drawing.Bitmap(objectData.TextureFilePath)
                        }
                    }
                };


            var texturedQuadEffect = new OpenGLAttributesEffect()
                {
                    Name = "Textured quad attributes"
                };
            texturedQuadEffect.AttributesGroup.EnableAttributes.EnableTexture2D = true;
            texturedQuadEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            texturedQuadEffect.AttributesGroup.ColorBufferAttributes.BlendingSourceFactor = BlendingSourceFactor.SourceAlpha;
            texturedQuadEffect.AttributesGroup.ColorBufferAttributes.BlendingDestinationFactor = BlendingDestinationFactor.OneMinusSourceAlpha;
            texturedQuadEffect.AttributesGroup.ColorBufferAttributes.EnableBlend = true;
            //texturedQuadEffect.AttributesGroup.LightingAttributes.TwoSided = true;
            texturedQuadEffect.AttributesGroup.TextureAttributes.TextureTarget = TextureTarget.Texture2D;
            texturedQuadEffect.AttributesGroup.TextureAttributes.TextureMinFilter = TextureMinFilter.Linear;
            texturedQuadEffect.AttributesGroup.TextureAttributes.TextureMagFilter = TextureMagFilter.Linear;
            texturedQuadEffect.AttributesGroup.CurrentAttributes.CurrentColor = objectData.Vertices[0].Color;

            texturedQuad.Effects.Add(texturedQuadEffect);

            return texturedQuad;
        }

        private Polygon CreateTriangleList(ObjectData objectData)
        {
            if (objectData.Faces.Count < 1)
                throw new Exception("Not enough data to create triangles list.");

            var triangleList = new Polygon()
            {
                Name = "Triangle list"
            };

            foreach (var vertexInfo in objectData.Vertices)
                triangleList.Vertices.Add(vertexInfo.Position);
            foreach (var uv in objectData.TextureCoordinates)
                triangleList.UVs.Add(uv);
            foreach (var face in objectData.Faces)
                triangleList.Faces.Add(face);

            triangleList.Material = new Material()
            {
                Diffuse = objectData.Vertices[0].Color
            };
            if (!string.IsNullOrWhiteSpace(objectData.TextureFilePath))
            {
                triangleList.Material.Texture = new Texture2D()
                {
                    Image = new System.Drawing.Bitmap(objectData.TextureFilePath)
                };
            }

            var triangleListEffect = new OpenGLAttributesEffect()
            {
                Name = "Triangle list attributes"
            };
            if (triangleList.Material.Texture != null)
            {
                triangleListEffect.AttributesGroup.EnableAttributes.EnableTexture2D = true;
                triangleListEffect.AttributesGroup.TextureAttributes.TextureTarget = TextureTarget.Texture2D;
                triangleListEffect.AttributesGroup.TextureAttributes.TextureMinFilter = TextureMinFilter.Linear;
                triangleListEffect.AttributesGroup.TextureAttributes.TextureMagFilter = TextureMagFilter.Linear;
            }

            triangleList.Effects.Add(triangleListEffect);

            return triangleList;
        }

        private GLColor MapValueToGLColor(float value, float min = 0.0f, float max = 1.0f)
        {
            const float redScaleFactor = 0.5f;
            const float greenScaleFactor = 1.0f;
            const float blueScaleFactor = 1.0f;

            // Map [min; max] to [0.0; 1.0].
            //float k = 1.0f / (max - min);
            //float b = -k * min;
            //value = k * value + b;
            value = (value - min) / (max - min);

            // Check the value.
            if (value < 0.0f)
                value = 0.0f;
            else if (value > 1.0f)
                value = 1.0f;

            value *= (float)Math.PI;

            return new GLColor(
                (float)Math.Sin(value * redScaleFactor) * 0.5f + 0.5f,
                (float)Math.Sin(value * greenScaleFactor) * 0.5f + 0.5f,
                (float)Math.Cos(value * blueScaleFactor) * 0.5f + 0.5f,
                1.0f);
        }
        
        private float ParseAngle(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for angle definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for angle definition.");

            return float.Parse(lineParts[1], nfi);
        }

        private GLColor ParseColor(string[] lineParts)
        {
            if (lineParts.Length < 4)
                throw new Exception("Not enough parameters for color definition.");
            else if (lineParts.Length > 5)
                throw new Exception("To many parameters for color definition.");

            float alpha = defaultColor.A;
            if (lineParts.Length == 5)
                alpha = float.Parse(lineParts[4], nfi);

            return new GLColor(float.Parse(lineParts[1], nfi), float.Parse(lineParts[2], nfi), float.Parse(lineParts[3], nfi), alpha);
        }

        private float ParseColorLevel(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for color level definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for color level definition.");

            return float.Parse(lineParts[1], nfi);
        }

        private int[] ParseFace(string[] lineParts)
        {
            if (lineParts.Length < 4)
                throw new Exception("Not enough parameters for face definition.");
            else if (lineParts.Length > 4)
                throw new Exception("To many parameters for face definition.");

            return new int[] { int.Parse(lineParts[1]), int.Parse(lineParts[2]), int.Parse(lineParts[3]) };
        }

        private FillType ParseFillType(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for fill type definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for fill type definition.");

            switch (lineParts[1])
            {
                case "auto":
                    return FillType.Auto;

                case "solid":
                    return FillType.Solid;

                case "levels":
                    return FillType.Levels;

                default:
                    throw new Exception("Undefined fill type.");
            }
        }

        private Index ParseIndex(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for index definition.");
            else if (lineParts.Length > 4)
                throw new Exception("To many parameters for index definition.");

            int normalIndex = -1;
            if (lineParts.Length >= 3)
                normalIndex = int.Parse(lineParts[2]);

            int textureCoordinatesIndex = -1;
            if (lineParts.Length >= 4)
                textureCoordinatesIndex = int.Parse(lineParts[3]);

            return new Index(int.Parse(lineParts[1]), textureCoordinatesIndex, normalIndex);
        }

        private float ParseLineWidth(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for line width definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for line width definition.");

            return float.Parse(lineParts[1], nfi);
        }

        private Vector3D ParseNormal(string[] lineParts)
        {
            if (lineParts.Length < 4)
                throw new Exception("Not enough parameters for normal definition.");
            else if (lineParts.Length > 4)
                throw new Exception("To many parameters for normal definition.");

            return new Vector3D(float.Parse(lineParts[1], nfi), float.Parse(lineParts[2], nfi), float.Parse(lineParts[3], nfi));
        }

        private void ParseObjectEndDefinition(string[] lineParts)
        {
            if (lineParts.Length > 1)
                throw new Exception("To many parameters for jbject end definition.");
        }

        private ObjectType ParseObjectStartDefinition(string[] lineParts)
        {
            ObjectType objectType;

            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for object definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many arguments.");

            switch (lineParts[1])
            {
                case "point_list":
                    objectType = ObjectType.PointList;
                    break;
                case "directions":
                    objectType = ObjectType.Directions;
                    break;
                case "polyline":
                    objectType = ObjectType.Polyline;
                    break;
                case "solid_polyline":
                    objectType = ObjectType.SolidPolyline;
                    break;
                case "surface":
                    objectType = ObjectType.Surface;
                    break;
                case "triangle_list":
                    objectType = ObjectType.TriangleList;
                    break;
                case "textured_quad":
                    objectType = ObjectType.TexturedQuad;
                    break;
                case "text_2D":
                    objectType = ObjectType.Text2D;
                    break;
                case "text_3D":
                    objectType = ObjectType.Text3D;
                    break;
                default:
                    throw new Exception("Undefined object type.");
            }

            return objectType;
        }

        private float ParsePointSize(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for point size definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for point size definition.");

            return float.Parse(lineParts[1], nfi);
        }

        private int ParseSlices(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for slices definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for slices definition.");

            return int.Parse(lineParts[1]);
        }

        private int ParseStacks(string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for stacks definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for stacks definition.");

            return int.Parse(lineParts[1]);
        }

        private string ParseText(string line, string[] lineParts)
        {
            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for text definition.");
            //else if (lineParts.Length > 2)
            //    throw new Exception("To many parameters for text definition.");

            //return lineParts[1];
            return line.Substring(lineParts[0].Length + 1).Trim();
        }

        private UV ParseTextureCoordinates(string[] lineParts)
        {
            if (lineParts.Length < 3)
                throw new Exception("Not enough parameters for texture coordinates definition.");
            else if (lineParts.Length > 3)
                throw new Exception("To many parameters for texture coordinates definition.");

            return new UV(float.Parse(lineParts[1], nfi), float.Parse(lineParts[2], nfi));
        }

        private string ParseTexturePath(string[] lineParts, string defaultDir)
        {
            string path;

            if (lineParts.Length < 2)
                throw new Exception("Not enough parameters for texture path definition.");
            else if (lineParts.Length > 2)
                throw new Exception("To many parameters for texture path definition.");

            path = lineParts[1];
            string dir = Path.GetDirectoryName(path);
            if (dir == null || dir == string.Empty)
                path = defaultDir + "\\" + path;

            return path;
        }

        private Point3D ParseVertex(string[] lineParts, out bool used)
        {
            if (lineParts.Length < 4)
                throw new Exception("Not enough parameters for vertex definition.");
            else if (lineParts.Length > 5)
                throw new Exception("To many parameters for vertex definition.");

            used = true;
            if (lineParts.Length == 5)
                used = (int.Parse(lineParts[4]) == 0 ? false : true);

            return new Point3D(float.Parse(lineParts[1], nfi), float.Parse(lineParts[2], nfi), float.Parse(lineParts[3], nfi));
        }

        private Polygon PolygonFromVerticesSet(IEnumerable<VertexInfo> vertices)
        {
            TriangleNet.Geometry.InputGeometry inputGeometry = new TriangleNet.Geometry.InputGeometry(vertices.Count());
            foreach (var vertex in vertices)
                inputGeometry.AddPoint(vertex);

            TriangleNet.Mesh mesh = new TriangleNet.Mesh();
            mesh.Triangulate(inputGeometry);

            var verticesInfo = mesh.Vertices.Select(v => (VertexInfo)v).ToList();
            var polygon = new Polygon()
                {
                    Name = "Surface"
                };
            polygon.Vertices = new System.Collections.ObjectModel.ObservableCollection<Point3D>(verticesInfo.Select(v => v.Position));
            polygon.Faces = new List<Face>();
            foreach (var triangle in mesh.Triangles)
            {
                if ((verticesInfo[triangle.P0].Used == true) &&
                    (verticesInfo[triangle.P1].Used == true) &&
                    (verticesInfo[triangle.P2].Used == true))
                {
                    Face face = new Face();
                    face.Indices.Add(new Index(triangle.P0));
                    face.Indices.Add(new Index(triangle.P1));
                    face.Indices.Add(new Index(triangle.P2));

                    polygon.Faces.Add(face);
                }
            }

            return polygon;
        }

        private bool TryReadLine(StreamReader reader, ref int lineIndex, out string line, out string[] tokens)
        {
            tokens = null;
            line = null;

            if (reader.EndOfStream)
            {
                return false;
            }

            line = reader.ReadLine().Trim();
            lineIndex++;

            while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
            {
                if (reader.EndOfStream)
                {
                    line = null;
                    return false;
                }

                line = reader.ReadLine().Trim();
                lineIndex++;
            }

            tokens = line.Split(splitSeparators, StringSplitOptions.RemoveEmptyEntries);

            return true;
        }

        #endregion Private Methods

        #region Public Constructors

        public ModelFileFormat()
        {
        }

        #endregion Public Constructors

        #region Public Methods

        public Scene LoadData(string path)
        {
            Scene scene = new Scene();
            SmartAxies sceneObjectsRoot = new SmartAxies();

            // Add root for all new scene objects
            scene.SceneContainer.Children.Add(sceneObjectsRoot);
            using (StreamReader reader = new StreamReader(path))
            {
                ObjectData objectData = new ObjectData();

                string defaultDir = Path.GetDirectoryName(path);

                GLColor currentColor = defaultColor;
                int currentNormalIndex = -1;
                int currentTextureCoordinateIndex = -1;

                string line = null;
                string[] tokens = null;
                int lineIndex = 0;
                bool error = false;
                try
                {
                    while (TryReadLine(reader, ref lineIndex, out line, out tokens) && !error)
                    {
                        try
                        {
                            if (tokens[0] == "OBJECT")
                            {
                                ObjectType objectType = ParseObjectStartDefinition(tokens);

                                if (objectData.ObjectType != ObjectType.None)
                                    throw new Exception("Can't define new object inside another.");

                                objectData.ObjectType = objectType;
                            }
                            else if (tokens[0] == "END")
                            {
                                ParseObjectEndDefinition(tokens);

                                try
                                {
                                    if (objectData.ObjectType == ObjectType.None)
                                        throw new Exception("Find \"END\" statement without object definition.");

                                    objectData.Validate();

                                    SceneElement sceneElement = CreateSceneElement(objectData);
                                    sceneObjectsRoot.Children.Add(sceneElement);
                                }
                                finally
                                {
                                    objectData.Reset();

                                    currentColor = defaultColor;
                                    currentNormalIndex = -1;
                                    currentTextureCoordinateIndex = -1;
                                }
                            }
                            else
                            {
                                if (objectData.ObjectType == ObjectType.None)
                                    throw new Exception("Found declaration out of object.");

                                switch (tokens[0])
                                {
                                    case "color":
                                        currentColor = ParseColor(tokens);
                                        break;

                                    case "point_size":
                                        if (objectData.PointSize.HasValue)
                                            throw new Exception("Point size already defined.");

                                        objectData.PointSize = ParsePointSize(tokens);

                                        break;

                                    case "line_width":
                                        if (objectData.LineWidth.HasValue)
                                            throw new Exception("Line width already defined.");

                                        objectData.LineWidth = ParseLineWidth(tokens);

                                        break;

                                    case "angle":
                                        if (objectData.Angle.HasValue)
                                            throw new Exception("3D text rotation angle already defined.");

                                        objectData.Angle = ParseAngle(tokens);

                                        break;

                                    case "slices":
                                        if (objectData.Slices.HasValue)
                                            throw new Exception("Number of slices already defined.");

                                        objectData.Slices = ParseSlices(tokens);

                                        break;

                                    case "stacks":
                                        if (objectData.Stacks.HasValue)
                                            throw new Exception("Number of stacks already defined.");

                                        objectData.Stacks = ParseStacks(tokens);

                                        break;

                                    case "text":
                                        if (objectData.Text != null)
                                            throw new Exception("Text string already defined.");

                                        objectData.Text = ParseText(line, tokens);

                                        break;

                                    case "texture_file":
                                        if (objectData.TextureFilePath != null)
                                            throw new Exception("Texture file path already defined.");

                                        objectData.TextureFilePath = ParseTexturePath(tokens, defaultDir);

                                        break;

                                    case "fill_type":
                                        if (objectData.ObjectType != ObjectType.Surface)
                                            throw new Exception("Fill type can be defined only for surface.");

                                        objectData.Fill = ParseFillType(tokens);

                                        break;

                                    case "color_level":
                                        if (objectData.ObjectType != ObjectType.Surface)
                                            throw new Exception("Color level can be defined only for surface.");

                                        objectData.ColorLevels.Add(ParseColorLevel(tokens), currentColor);

                                        break;
                                    
                                    case "v":
                                        bool used;
                                        Point3D vertex = ParseVertex(tokens, out used);
                                        VertexInfo vertexInfo = new VertexInfo(currentColor, vertex, currentNormalIndex, currentTextureCoordinateIndex, used);
                                        objectData.Vertices.Add(vertexInfo);

                                        break;

                                    case "vn":
                                        Vector3D normal = ParseNormal(tokens);
                                        currentNormalIndex = objectData.Normals.Count;
                                        objectData.Normals.Add(normal);

                                        break;

                                    case "vt":
                                        UV textureCoordinates = ParseTextureCoordinates(tokens);
                                        currentTextureCoordinateIndex = objectData.TextureCoordinates.Count;
                                        objectData.TextureCoordinates.Add(textureCoordinates);

                                        break;

                                    case "index":
                                        Index index = ParseIndex(tokens);
                                        if (index.Vertex >= 0 && index.Vertex >= objectData.Vertices.Count)
                                            throw new Exception("Vertex index out of range.");
                                        if (index.Normal >= 0 && index.Normal >= objectData.Normals.Count)
                                            throw new Exception("Normal index out of range.");
                                        if (index.UV >= 0 && index.UV >= objectData.TextureCoordinates.Count)
                                            throw new Exception("Texture coordinate index out of range.");

                                        objectData.Indices.Add(index);

                                        break;

                                    case "face":
                                        int[] faceIndices = ParseFace(tokens);
                                        Face face = new Face();
                                        foreach (int faceIndex in faceIndices)
                                        {
                                            if (faceIndex >= 0 && faceIndex < objectData.Indices.Count)
                                                face.Indices.Add(objectData.Indices[faceIndex]);
                                            else
                                                throw new Exception("Index out of range.");
                                        }
                                        objectData.Faces.Add(face);

                                        break;

                                    default:
                                        throw new Exception("Found unknown parameter declaration.");
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            string errorString = "Error while reading line " + lineIndex.ToString() + ": " + e.Message + "\nContinue?";
                            if (MessageBox.Show(errorString, "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Error) != DialogResult.Yes)
                                error = true;
                        }
                    }

                    if (objectData.ObjectType != ObjectType.None)
                        throw new Exception("Found object declaration without \"END\" statement.");
                }
                catch (Exception e)
                {
                    string errorString = "Error while reading line " + lineIndex.ToString() + ": " + e.Message + "\nContinue?";
                    MessageBox.Show(errorString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }

            return scene;
        }

        public bool SaveData(Scene scene, string path)
        {
            throw new NotImplementedException();
        }

        #endregion Public Mrthods

        #region Public Properties

        public string[] FileTypes
        {
            get { return new string[] { "mdl" }; }
        }

        public string Filter
        {
            get { return "Model Files (*.mdl)|*.mdl"; }
        }

        #endregion Public Properties
    }

    ﻿class Secret
     {
         private int SecretValue;

         public bool Eaguals(Secret other)
         {
             return other.SecretValue == SecretValue;
         }

         public void Corrupt(Secret other)
         {
             other.SecretValue = -1;
         }
     }
}
